use std::sync::Arc;

use axum::body::Bytes;
use axum::http::header::CONTENT_LENGTH;
use axum::http::HeaderMap;
use axum::{extract::Multipart, http::StatusCode, response::Redirect, BoxError};
use futures::stream::once;
use futures::{Stream, StreamExt, TryStreamExt};
use tokio::{
    fs::File,
    io::{self, BufWriter},
};
use tokio_util::io::StreamReader;

pub(crate) async fn upload(
    headers: HeaderMap,
    mut multipart: Multipart,
) -> Result<Redirect, (StatusCode, String)> {
    while let Ok(Some(field)) = multipart.next_field().await {
        let filename = if let Some(filename) = field.file_name() {
            println!("The filename is:{}", filename);
            filename.to_owned()
        } else {
            continue;
        };
        let total = headers
            .get(CONTENT_LENGTH)
            .unwrap()
            .to_str()
            .unwrap()
            .parse::<usize>()
            .unwrap();
        stream_to_file(&filename, field, total).await?;
    }
    Ok(Redirect::to("/"))
}

async fn stream_to_file<S, E>(
    path: &str,
    stream: S,
    total: usize,
) -> Result<(), (StatusCode, String)>
where
    S: Stream<Item = Result<Bytes, E>>,
    E: Into<BoxError>,
{
    async {
        let progress = ml_progress::progress!(total).unwrap();
        let progress = Arc::new(progress);
        let body_with_io_error = stream
            .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
            .inspect_ok({
                let progress = Arc::clone(&progress);
                move |v| {
                    let size = v.len();
                    progress.inc(size as u64);
                }
            })
            .chain(once(async move {
                progress.finish();
                Ok(axum::body::Bytes::new())
            }));
        let body_reader = StreamReader::new(body_with_io_error);
        futures::pin_mut!(body_reader);
        let path = std::path::Path::new(".").join(path);
        let mut file = BufWriter::new(File::create(path).await?);
        tokio::io::copy(&mut body_reader, &mut file).await?;
        Ok::<_, io::Error>(())
    }
    .await
    .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))
}
